using System;
using System.ComponentModel;
using System.IO;
using System.Threading;
using System.Windows.Forms;
using System.Collections.Generic;
using Genghis;
using SpeechLib;
using System.Drawing;
using TeleTwitter.Lib;
using TeleTwitter.Properties;

namespace TeleTwitter
{
    public partial class MainForm : Form
    {
        private bool userNameSetFlag = false;
        private BrowserController _controller = new BrowserController();
        private WindowSerializer _windowSerializer = null;
        private TimeSpan _timeRemaining;
    	private SpeechVoiceSpeakFlags _speechFlags = SpeechVoiceSpeakFlags.SVSFlagsAsync;
    	private SpVoice _voice;
        private List<Timeline> _timelines = new List<Timeline>();
        private bool _hasUpdates = false;


       private struct UpdateStatusArgs
       {
          public string text;
          public bool updateTwitter;
          public bool updateJaiku;
          public bool updatePownce;
       }

        public MainForm()
        {
            InitializeComponent();
            _windowSerializer = new WindowSerializer(this);
        }

        private void MainForm_Load(object sender, EventArgs e)
        {
            // Check the configuration settings
            CheckConfiguration();

            // Initialize the controls
            InitControls();

            // Init Speech stuff
            _voice = new SpVoice();
            _voice.Volume = Settings.Default.VoiceVolume;
            _voice.Rate = Settings.Default.VoiceRate;

            try
            {
                if (!(string.IsNullOrEmpty(Settings.Default.SelectedVoice)) && Settings.Default.SelectedVoice != "Default")
                {
                    _voice.Voice = _voice.GetVoices("Name=" + Settings.Default.SelectedVoice, "").Item(0);
                }
            }
            catch
            {
                //do nothing, just use the default voice
            }

            notifyIcon.Text = this.Text;
            notifyIcon.Icon = this.Icon;
        }

        #region Config/Init Support

        private void CheckConfiguration()
        {
            // Make sure the user has entered a username and password.
            if (Settings.Default.UserName == string.Empty || Settings.Default.Password == string.Empty)
            {
                MessageBox.Show("Welcome to TeleTwitter!\n\nThis appears to be the first time you've run this application. In the following screen, please enter your user name and password to get started.");
                using (OptionsForm optionsForm = new OptionsForm())
                {
                    optionsForm.ShowDialog(this);
                }
            }
        }

        private void InitControls()
        {
            try
            {
                // Setting default to XML. I think the JSON stuff is having issues periodically.
                TwitterManager.Instance().TwitterApi = (ApiType)Properties.Settings.Default.ApiType;
            }
            catch
            {
            }
			   TwitterManager.Instance().Username = Properties.Settings.Default.UserName;
			   TwitterManager.Instance().Password = Properties.Settings.Default.Password;

            JaikuManager.Instance().Username = Properties.Settings.Default.JaikuUserName;
            JaikuManager.Instance().Password = Properties.Settings.Default.JaikuAPIKey;
            JaikuManager.Instance().JaikuApi = ApiType.JSON;

            PownceManager.Instance().Username = Properties.Settings.Default.PownceUserName;
            PownceManager.Instance().Password = Properties.Settings.Default.PowncePassword;
            PownceManager.Instance().PownceApi = ApiType.XML;

            // Setup the text box
            _controller.UserNameChanged +=new BrowserController.UserNameChangedHandler(_controller_UserNameChanged);

            // Setup the web browser
            webBrowser1.ObjectForScripting = _controller;
            webBrowser1.DocumentText = _controller.GetTimelines();

            // Initialize the timelines we'll be tracking.
            Timeline t = new Timeline();
            t.TimelineName = "Friends";
            t.ThisTimelineType = TimelineType.AllFriends;
            t.HtmlContainerId = "tlcontainer";
            _timelines.Add(t);
  
            t = new Timeline();
            t.TimelineName = "Replies";
            t.ThisTimelineType = TimelineType.Replies;
            t.HtmlContainerId = "rtlcontainer";
            _timelines.Add(t);

            // Only enable the public timeline thread if the user chooses so.
            if (Properties.Settings.Default.UsePublicTimeline)
            {
                t = new Timeline();
                t.TimelineName = "Everyone";
                t.ThisTimelineType = TimelineType.Public;
                t.HtmlContainerId = "ptlcontainer";
                _timelines.Add(t);
            }

            // Get the current tweets and set the refresher
            backgroundGetLatest.RunWorkerAsync();
			_timeRemaining = new TimeSpan(0, Settings.Default.RefreshDelay, 0);
			refreshTimer.Interval = 1000;
        	refreshTimer.Enabled = true;

            

            // Setup the menu
            newStatusTextBox.TextChanged += new EventHandler(newStatusTextBox_TextChanged);
            newStatusTextBox.KeyDown += new KeyEventHandler(newStatusTextBox_Key);
            newStatusTextBox.KeyUp += new KeyEventHandler(newStatusTextBox_KeyUp);
            newStatusTextBox.KeyPress += new KeyPressEventHandler(newStatusTextBox_KeyPress);
            newStatusTextBox.MaxLength = 140;
            hideMenuToolStripMenuItem.Click += new EventHandler(showToolStripMenuItem_Click);
            optionsToolStripMenuItem.Click += new EventHandler(optionsToolStripMenuItem_Click);
            refreshToolStripMenuItem.Click += new EventHandler(refreshToolStripMenuItem_Click);
        	RefreshRemainingCharacters();

            menuStrip1.Visible = Settings.Default.MenuVisibility;
        }

        #endregion

        #region Status Box Support

        //lets us know if the user has entered any text into the textbox manually
        void newStatusTextBox_TextChanged(object sender, EventArgs e)
        {
            userNameSetFlag = newStatusTextBox.Text.Length > 0;
        }

        void newStatusTextBox_KeyPress(object sender, KeyPressEventArgs e)
        {
            if (e.KeyChar == Convert.ToChar(1))
            {
                newStatusTextBox.SelectAll();
                e.Handled = true;
            }
        }

        //Wires up the interaction from the controller to winform. We want to 
        //allow the user to click on differnt avtars, but we do not want them to overwrite
        //any text they manually entered.
        void _controller_UserNameChanged(object sender, BrowserController.UserNameChangeEventArguments e)
        {
            string username = sender as string;
            if (username != null)
            {
                if(!userNameSetFlag)
                {
                    string mask = "{0}{1} : ";
                    newStatusTextBox.Text = string.Format(mask, e.CommandText, username);

                    userNameSetFlag = false;
                    newStatusTextBox.Focus();
                    newStatusTextBox.Select(newStatusTextBox.Text.Length, 0);
                }
            }
        }

        #endregion

        #region Menu Support

        void refreshToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (!backgroundGetLatest.IsBusy)
            {
                backgroundGetLatest.RunWorkerAsync();
            }
        }

        static void optionsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            using (OptionsForm form = new OptionsForm())
            {
                form.ShowDialog();
            }
        }

        void showToolStripMenuItem_Click(object sender, EventArgs e)
        {
            bool visible = !menuStrip1.Visible;
            menuStrip1.Visible = visible;
            Settings.Default.MenuVisibility = visible;
            Settings.Default.Save();
        }

        private void closeToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private void helpToolStripMenuItem_Click(object sender, EventArgs e)
        {
            using (HelpForm form = new HelpForm())
            {
                form.ShowDialog();
            }
        }

        #endregion

        #region Update Status

        static void BackUpMessage(string text)
        {
            using (StreamWriter streamWriter = new StreamWriter(Path.Combine(Environment.CurrentDirectory, "message.log"), true))
            {
                streamWriter.WriteLine("Message sent: " + DateTime.Now);
                streamWriter.WriteLine(text);
                streamWriter.WriteLine();
            }
        }

        void newStatusTextBox_Key(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Return)
            {
                e.SuppressKeyPress = true;

                if (newStatusTextBox.Text.Length > 2)
                {
                    webBrowser1.Document.InvokeScript("setMessage", new object[] { "Please wait, updating status..." });
                    
                    string text = newStatusTextBox.Text;

                   UpdateStatusArgs statusArgs = new UpdateStatusArgs();
                   statusArgs.text = text;
                   statusArgs.updateTwitter = chkTwitter.Checked;
                   statusArgs.updateJaiku = chkJaiku.Checked;
                   statusArgs.updatePownce = chkPownce.Checked;

                    BackUpMessage(text);
                    newStatusTextBox.Clear();
                    newStatusTextBox.Refresh();
                    RefreshRemainingCharacters();

                    if (!backgroundUpdateStatus.IsBusy)
                    {
                        backgroundUpdateStatus.RunWorkerAsync(statusArgs);
                    }

                    webBrowser1.Document.InvokeScript("setMessageAndClear", new object[] { "Status updated. There may be a slight delay before you see it below." });
                }
            }
        }

        void newStatusTextBox_KeyUp(object sender, KeyEventArgs e)
        {
        	RefreshRemainingCharacters();
        }

		private void RefreshRemainingCharacters()
		{
            lblCharsLeft.Text = "-" + (140 - newStatusTextBox.Text.Length) + "-";
		}

		private void SetContent(string output, string container, Timeline timeLine)
		{
			webBrowser1.Document.InvokeScript("setContents", new object[] {output, container});

			
            if (_hasUpdates)
			{
                if (Settings.Default.PlaySound)
                {
                    string path = Path.Combine(Environment.CurrentDirectory, "AgilityOrb.wav");
                    Sound.Play(path);
                }
				if (Settings.Default.VoiceEnabled)
				{

					//Process text output
					// - Put items in reverse order
					// - @username should translate to "Jason Alexander says to Scott Cate"
					// - pauses between tweets
					// - phrase URLs differently
					
                    //TODO: Fix this to work with the new timeline object model
                    _voice.Speak(VoiceTranslator.Translate(timeLine, _timelines), _speechFlags);
				}
                if (Settings.Default.MinimizeToTray && notifyIcon.Visible)
                {
                    ToastForm.ShowToast("There are new tweets!", null, new ToastTargetClickHandler(toastSelect));
                }
                _hasUpdates = false;
			}
		}

    	protected static void LogError(string title, string message)
        {
            using (StreamWriter streamWriter = new StreamWriter(Path.Combine(Environment.CurrentDirectory, "error.log"), true))
            {
                streamWriter.WriteLine();
                streamWriter.WriteLine(title + " at " + DateTime.Now);
                streamWriter.WriteLine(message);
                streamWriter.WriteLine();
                streamWriter.Close();
            }
        }

        protected static void LogError(string title, Exception ex)
        {
            using (StreamWriter streamWriter = new StreamWriter(Path.Combine(Environment.CurrentDirectory, "error.log"), true))
            {
                streamWriter.WriteLine();
                streamWriter.WriteLine(title + " at " + DateTime.Now);
                streamWriter.WriteLine("Exception:");
                streamWriter.WriteLine(ex.Message);
                streamWriter.WriteLine();
                streamWriter.WriteLine("Stack Trace:");
                streamWriter.WriteLine(ex.StackTrace);
                streamWriter.WriteLine();
                streamWriter.Close();
            }
        }

    	#endregion

        #region TinyURL Support

        private void getTinyURLToolStripMenuItem_Click(object sender, EventArgs e)
        {
            string selectedText = newStatusTextBox.SelectedText;
            if (string.IsNullOrEmpty(selectedText))
            {
                return;
            }
            Uri newUri;
            if (!Uri.TryCreate(selectedText, UriKind.Absolute, out newUri))
            {
                return;
            }
            string tinyUrl = TinyUrl.GetTinyURL(selectedText);
            newStatusTextBox.SelectedText = tinyUrl;
        }

        #endregion

        #region Thread code

        private void backgroundGetLatest_DoWork(object sender, DoWorkEventArgs e)
		{
			bool successful = false;
			backgroundGetLatest.ReportProgress(0);

			while (!successful)
			{
                foreach (Timeline t in _timelines)
                {
                    try
                    {
                        t.StatusList = _controller.GetTimeline(t);
                        t.TimelineOutput = StatusFormatter.FormatTimelineHtml(t.StatusList);
                        if (t.ThisTimelineType == TimelineType.AllFriends && t.TimelineOutput.Length > 0) { _hasUpdates = true; }
                    }
                    catch (Exception ex)
                    {
                        LogError("Refreshing timeline", ex);
                        backgroundGetLatest.ReportProgress(1);
                        Thread.Sleep(10000);
                    }
                }
                successful = true;
			}
		}

		private void backgroundGetLatest_RunWorkerComplete(object sender, RunWorkerCompletedEventArgs e)
		{
			// Reset refresh timer
			_timeRemaining = new TimeSpan(0, Settings.Default.RefreshDelay, 0);
			refreshTimer.Enabled = true;

            // Parse statuses for direct messages and update each timeline on the display.
            foreach (Timeline timeline in _timelines)
            {
                SetContent(timeline.TimelineOutput,timeline.HtmlContainerId, timeline);
                foreach (Status status in timeline.StatusList)
                {
                    if (status.Text.ToLower().Contains("@" + TwitterManager.Instance().CurrentUser.ScreenName.ToLower()))
                    {
                        // Check to see if this message has been processed before
                        if (!Properties.Settings.Default.ProcessedDirectMessages.Contains(status.Id))
                        {
                            //DirectMessageForm direct = new DirectMessageForm();
                            //direct.Show(this);
                            //direct.CurrentUser = TwitterManager.Instance().CurrentUser;
                            //direct.AddStatus(status);

                            //// Now, add it to the processed queue.
                            //Properties.Settings.Default.ProcessedDirectMessages.Add(status.Id);
                            //Properties.Settings.Default.Save();
                        }
                    }
                }
            }
		}

		private void backgroundGetLatest_ProgressChanged(object sender, ProgressChangedEventArgs e)
		{
			switch (e.ProgressPercentage)
			{
				case 0:
					refreshTimer.Enabled = false;
					toolStripStatusLabel2.Text = "Please wait, refreshing timeline...";
					break;
				case 1:
					toolStripStatusLabel2.Text = "Error on last refresh. TeleTwitter will try again soon.";
					break;
			}
		}

		private void refreshTimer_Tick(object sender, EventArgs e)
		{
			_timeRemaining = new TimeSpan(_timeRemaining.Ticks - TimeSpan.TicksPerSecond);
			if (_timeRemaining.Ticks > TimeSpan.TicksPerSecond)
			{
                string timeBreakdown = (_timeRemaining.Minutes > 0 ? _timeRemaining.Minutes + "m" : "");
                timeBreakdown += _timeRemaining.Seconds + "s";

                toolStripStatusLabel2.Text = string.Format("Refresh in {0}...", timeBreakdown);
				refreshTimer.Enabled = true;
			}
			else
			{
                if (!backgroundGetLatest.IsBusy)
                {
                    backgroundGetLatest.RunWorkerAsync();
                }
			}
		}

		private void backgroundUpdateStatus_DoWork(object sender, DoWorkEventArgs e)
		{
			bool successful = false;

		   UpdateStatusArgs args = (UpdateStatusArgs) e.Argument;

         string text = args.text;

			if (text == null)
			{
			    return;
			}
			backgroundUpdateStatus.ReportProgress(0);

			while (!successful)
			{
				try
				{
					_controller.UpdateUserStatus(text, args.updateTwitter, args.updateJaiku, args.updatePownce);
					successful = true;
				}
				catch (Exception ex)
				{
					LogError("Updating status", ex);
					backgroundUpdateStatus.ReportProgress(1);
					Thread.Sleep(10000);
				}
			}
		}

		private void backgroundUpdateStatus_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
		{
			toolStripStatusLabel3.Text = string.Empty;
			_timeRemaining = new TimeSpan(0, 0, 5);
		}

		private void backgroundUpdateStatus_ProgressChanged(object sender, ProgressChangedEventArgs e)
		{
			switch(e.ProgressPercentage)
			{
				case 0:
					toolStripStatusLabel3.Text = "Updating status...";
					break;
				case 1:
					toolStripStatusLabel3.Text = "Error on status update. TeleTwitter will try again soon.";
					break;
			}
		}

		#endregion

        #region Systray Support

        private void notifyIcon_DoubleClick(object sender, EventArgs e)
        {
            this.Visible = true;
            this.WindowState = FormWindowState.Normal;
            notifyIcon.Visible = false;
        }
        private void MainForm_SizeChanged(object sender, EventArgs e)
        {
            if (Settings.Default.MinimizeToTray && this.WindowState == FormWindowState.Minimized)
            {
                notifyIcon.Visible = true;
                this.Visible = false;
            }
        }

        #endregion

        #region Toast Support

        private void toastSelect(object sender, ToastSelectEventArgs args)
        {
            this.Visible = true;
            this.WindowState = FormWindowState.Normal;
            notifyIcon.Visible = false;
        }
        #endregion

        private void webBrowser1_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
        {
            if (!newStatusTextBox.Focused) newStatusTextBox.Focus();
        }
       
	}
}
